package mtm.ui.panel;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;

import mtm.constants.Constants;
import mtm.date.MtmDate;
import mtm.db.DatabaseHandler;
import mtm.db.Job;
import mtm.ui.job.JobFrame;
import mtm.ui.resizable.ResizableLabel;

/**
 * A panel to display a week
 * 
 * @author CondorHero89
 * 
 */
public class WeekPanel extends JPanel {

	public static final int WIDTH_OF_CELL = 100;
	public static final int HEIGHT_OF_CELL = 24;
	public static final int BORDER_WIDTH = 40;
	public static final int BORDER_HEIGHT = 20;
	
	private static final int DAY_BORDER = 5;

	private static final BasicStroke dashed = new BasicStroke(1.0f,
			BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f,
			new float[] { 1.0f }, 0.0f);
	
	// database handler
	private DatabaseHandler handler = DatabaseHandler.getInstance();
	
	// singleton instance
	private static WeekPanel instance = null;

	// private ResizableLabel currentLabel;
	private static ArrayList<ResizableLabel> labelsList = new ArrayList<ResizableLabel>();

	// the first day of week
	private MtmDate firstDayOfWeek;

	private ResizableLabel intersectedLabel; // the label is intesected with
												// another label
	
	
	/**
	 * Singleton instance
	 */
	public static WeekPanel getInstance() {
		return instance == null ? new WeekPanel() : instance;
	}


	/**
	 * Constructor
	 */
	private WeekPanel() {
		// null layout
		super(null);
		instance = this;

		// set hand cursor
		setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));

		// process mouse event
		addMouseListener(mouseListenerForPanel);
		addMouseMotionListener(mouseListenerForPanel);
		
		// load jobs in current week
		displayJobsInWeek(MtmDate.getFirstDayOfCurrentWeek());
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		
		String[] timeData = new String[25];
		for (int h = 0; h < timeData.length; ++h) {
			timeData[h] = (h < 10) ? ("0" + h + ":00") : (h + ":00");
		}

		// draw hours
		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(Color.magenta);
		for (int i = 0; i < timeData.length; ++i) {
			g2.drawString(timeData[i], 0, i * HEIGHT_OF_CELL + BORDER_HEIGHT);
		}
		
		g2.setColor(Color.blue);
		
		// draw days
		int month = firstDayOfWeek.getMonth();
		int firstDate = firstDayOfWeek.getDate();
		MtmDate dateOfWeek = new MtmDate(firstDayOfWeek);
		for (int i = 0; i < 7; ++i) {
//			if (i == selectedDay - 1) {
//				continue;
//			}
			g2.drawString(
					Constants.NORMAL_DAYS[i] + " " + (dateOfWeek.getDate()) + "/" + dateOfWeek.getMonth(), 
					BORDER_WIDTH + i * WIDTH_OF_CELL + DAY_BORDER, 
					BORDER_HEIGHT * 2 / 3);
			dateOfWeek.addDate(1); 
		}
		
		// draw columns
		g2.translate(BORDER_WIDTH, BORDER_HEIGHT);
		g2.setColor(Color.black);
		for (int i = 0; i <= 7; ++i) {
			g2.drawLine(i * WIDTH_OF_CELL, 0, i * WIDTH_OF_CELL, 24 * HEIGHT_OF_CELL);
		}
		
		// draw rows
		for (int i = 0; i <= 24; i += 2) {
			g2.drawLine(0, i * HEIGHT_OF_CELL, 7 * WIDTH_OF_CELL, i * HEIGHT_OF_CELL);
		}
		g2.setStroke(dashed);
		for (int i = 1; i <= 24; i += 2) {
			g2.drawLine(0, i * HEIGHT_OF_CELL, 7 * WIDTH_OF_CELL, i * HEIGHT_OF_CELL);
		}

		// TODO draw labels
		// you must determine the current label
		for (ResizableLabel label : labelsList) {
			if (label.isIntersected()) {
				intersectedLabel = label;
			} else {
				label.repaint();
			}
		}
		// draw the intersected label
		if (intersectedLabel != null)
			intersectedLabel.repaint();
	}

	
	/**
	 * Return an array list of all label's bound on the panel which is not
	 * moving
	 * 
	 * @return
	 */
	public static ArrayList<Rectangle> getLabelsBound(Rectangle currentBound) {
		ArrayList<Rectangle> result = new ArrayList<Rectangle>();
		for (ResizableLabel lbl : labelsList) {
			if (!lbl.getBounds().equals(currentBound)) {
				result.add(lbl.getBounds());
			}
		}

		return result;
	}
	
	
	/**
	 * Return first day of week
	 * @return
	 */
	public MtmDate getFirstDayOfWeek() {
		return firstDayOfWeek;
	}
	
	
	/**
	 * Load all jobs from Db which is correlative with this week
	 */
	public void reloadJobs() {
		// clear old jobs on screen first
		removeAllLabels();
		// then add new jobs
		ArrayList<Job> jobList = handler.getJobsInWeek(firstDayOfWeek);
		for (Job job : jobList) {
			addJob(job);
		}
	}

	
	/**
	 * Add a job to this week panel
	 * 
	 * @param job
	 */
	public void addJob(Job job) {
		// check for time of  job this correct for this week
		MtmDate date = new MtmDate(firstDayOfWeek);
		MtmDate dateOfJob = job.getStartTime();
		if (date.compareTo(dateOfJob) > 0) {
			return;
		}
		date.addDate(6);
		date.setHours(23);
		date.setMinutes(59);
		if (date.compareTo(dateOfJob) < 0) {
			return;
		}
		
		// calculate start and end time of job
		float startTime = job.getStartTime().getHours()
				+ (float) job.getStartTime().getMinutes() / 60;
		float endTime = job.getEndTime().getHours()
				+ (float) job.getEndTime().getMinutes() / 60;
		
		// calculate position of label on screen
		int y = (int) (startTime * HEIGHT_OF_CELL) + BORDER_HEIGHT;
		int x = (job.getStartTime().getDay() - 1) * WIDTH_OF_CELL + BORDER_WIDTH;
		int h = (int) ( (endTime - startTime) * HEIGHT_OF_CELL );

		// create a new label
		final ResizableLabel lbl = new ResizableLabel(job);
		lbl.setBounds(x, y, WIDTH_OF_CELL, h);
		add(lbl);
		repaint();
		
		// add to label list
		labelsList.add(lbl);
	}
	
	
	/**
	 * Update a job
	 * @param newJob
	 * @param jobId
	 */
	public void updateJob(Job newJob, int jobId) {
		removeJob(jobId);
		addJob(newJob);
	}
	
	
	/**
	 * Remove all label of this panel
	 */
	public void removeAllLabels() {
		for (ResizableLabel lbl : labelsList) {
			remove(lbl);
			repaint();
		}
		labelsList.clear();
	}

	
	/**
	 * Remove a job from week panel
	 */
	public void removeJob(int jobId) {
		for (ResizableLabel lbl : labelsList) {
			if (lbl.getId() == jobId) {
				remove(lbl);
				labelsList.remove(lbl);
				repaint();
				return;
			}
		}
	}
	
	
	/**
	 * Remove a RepeatJob
	 * @param parentId id of the RepeatJob
	 */
	public void removeRepeatJob(int parentId) {
		ArrayList<ResizableLabel> temp = new ArrayList<ResizableLabel>();
		for (ResizableLabel lbl : labelsList) {
			if (lbl.getJob().getParentId() == parentId) {
				remove(lbl);
				temp.remove(lbl);
			}
		}
		
		labelsList.removeAll(temp);
		
		repaint();
	}
	
	
	/**
	 * Display jobs in a week which has the correlative first day of week
	 * @param firstDayOfWeek
	 */
	public void displayJobsInWeek(MtmDate firstDayOfWeek) {
		this.firstDayOfWeek = firstDayOfWeek;
		reloadJobs();
	}

	
	/**
	 * Listener for mouse event
	 */
	MouseInputAdapter mouseListenerForPanel = new MouseInputAdapter() {
		private int x = -1, y = -1;
		private int h;
		private boolean isAllowCreateLabel = true;

		@Override
		public void mousePressed(MouseEvent e) {
			x = e.getX();
			y = e.getY();
			
			// don't allow to press at boundary of day grid
			if (x <= WeekPanel.BORDER_WIDTH
					|| x >= WeekPanel.BORDER_WIDTH + 7 * WeekPanel.WIDTH_OF_CELL
					|| y <= WeekPanel.BORDER_HEIGHT
					|| y >= WeekPanel.BORDER_HEIGHT + 24 * WeekPanel.HEIGHT_OF_CELL) {
				isAllowCreateLabel = false;
				Toolkit.getDefaultToolkit().beep();
			} else {
				isAllowCreateLabel = true;
			}
		}

		@Override
		public void mouseDragged(MouseEvent e) {
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (isAllowCreateLabel) {
				h = e.getPoint().y - y;
				processMouseDrag(x, y, h);
			}
		}
	};

	
	/**
	 * Process mouse drag event
	 * 
	 * @param x
	 * @param y
	 * @param h
	 */
	private void processMouseDrag(int x, int y, int h) {
		// calculate position of new label
		if (h < 0) {
			int yOld = y;
			h = -h;
			y = round(y - h, HEIGHT_OF_CELL) - HEIGHT_OF_CELL;
			x = round(x, WIDTH_OF_CELL);
			h = round((yOld - h) % HEIGHT_OF_CELL + h, HEIGHT_OF_CELL) + HEIGHT_OF_CELL;
		} else {
			int yOld = y;
			x = round(x, WIDTH_OF_CELL);
			y = round(y, HEIGHT_OF_CELL) - HEIGHT_OF_CELL;
			h = round((yOld - y) + h, HEIGHT_OF_CELL);
		}
		x += BORDER_WIDTH;
		y += BORDER_HEIGHT;

		// calculate start and end time of job
		MtmDate startTime = new MtmDate();
		startTime.setYear(firstDayOfWeek.getYear());
		startTime.setMonth(firstDayOfWeek.getMonth());
		startTime.setDate((x - BORDER_WIDTH) / WIDTH_OF_CELL + firstDayOfWeek.getDate());
		startTime.setHours((y - BORDER_HEIGHT) / HEIGHT_OF_CELL);
		startTime.setMinutes(y - BORDER_HEIGHT - startTime.getHours() * HEIGHT_OF_CELL);
		
		MtmDate endTime = new MtmDate();
		endTime.setYear(firstDayOfWeek.getYear());
		endTime.setMonth(firstDayOfWeek.getMonth());
		endTime.setDate((x - BORDER_WIDTH) / WIDTH_OF_CELL + firstDayOfWeek.getDate());
		endTime.setHours((y + h - BORDER_HEIGHT) / HEIGHT_OF_CELL);
		endTime.setMinutes(y + h - BORDER_HEIGHT - endTime.getHours() * HEIGHT_OF_CELL);
		
		// create a job
		Job job = new Job(Constants.DEFAULT_JOB_NAME, startTime, endTime, "");
		
		// insert to DB
		handler.insertJob(job);
		
		// create new label
		final ResizableLabel lbl = new ResizableLabel(job);
		lbl.setBounds(x, y, WIDTH_OF_CELL, h);
		
		// add to panel
		add(lbl);
		repaint();
		
		// add to label list
		labelsList.add(lbl);
	}


	/**
	 * Return [x/n]
	 */
	private int round(int x, int n) {
		return (x / n) * n;
	}
	
}
